package clearsyntax.types;

import static java.lang.Math.abs;

import java.util.Iterator;

public abstract class AbstractRange<E> implements Range<E> {

	private static final int UNCALCULATED = -1;

	private final E start;
	private final E end;
	private final int step;
	private int length;

	public AbstractRange(E start, E end, int step) {
		if (start == null) {
			throw new NullPointerException();
		}
		if (end == null) {
			throw new NullPointerException();
		}
		if (step == 0) {
			throw new IllegalArgumentException("Invalid: step == 0");
		}
		this.start = start;
		this.end = end;
		this.step = step;
		this.length = UNCALCULATED;
	}

	public abstract Range<E> by(int step);

	public abstract E get(int index);

	protected abstract int toCode(E value); 

	@Override
	public boolean contains(E value) {
		final int startCode = toCode(start);
		final int endCode = toCode(end);
		final int valueCode = toCode(value);

		final boolean inInterval = (step > 0)
								? (valueCode >= startCode) && (valueCode < endCode)
								: (valueCode <= startCode) && (valueCode > endCode); 

		return inInterval && (((valueCode - startCode) % step()) == 0);
	}

	protected int calculateLength() {
		final int startCode = toCode(start);
		final int endCode = toCode(end);
		if (startCode < endCode && step < 0) {
			return 0;
		}
		if (startCode > endCode && step > 0) {
			return 0;
		}
		final int interval = abs(startCode - endCode);
		final int absStep = abs(step);
		return (interval / absStep) + ((interval % absStep != 0) ? 1 : 0);
	}

	@Override
	public final Iterator<E> iterator() {
		return new Iterator<E>() {

			private int index = -1;

			public boolean hasNext() {
				return ++index < length;
			}

			public E next() {
				return get(index);
			}

			public void remove() {
				throw new UnsupportedOperationException();
			}

		};
	}

	@Override
	public final E start() {
		return start;
	}

	@Override
	public final E end() {
		return end;
	}

	@Override
	public final int step() {
		return step;
	}

	@Override
	public final int length() {
		if (length == UNCALCULATED) {
			length = calculateLength();
		}
		return length;
	}

	protected final void checkLength() {
		if (length() < 0) {
			throw new IllegalArgumentException("Invalid length: " + length);
		}
	}

	protected final void checkIndex(int index) {
		if (index < 0 || index >= length()) {
			throw new IndexOutOfBoundsException("Index out of range: " + index);
		}
	}

}